// Copyright (c) 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "ui/base/x/selection_utils.h"

#include <stdint.h>

#include <algorithm>
#include <set>

#include "base/i18n/icu_string_conversions.h"
#include "base/logging.h"
#include "base/strings/string_split.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "ui/base/clipboard/clipboard.h"
#include "ui/base/x/x11_util.h"
#include "ui/gfx/x/x11_atom_cache.h"

namespace ui {

const char kMimeTypeMozillaURL[] = "text/x-moz-url";
const char kString[] = "STRING";
const char kText[] = "TEXT";
const char kTextPlain[] = "text/plain";
const char kTextPlainUtf8[] = "text/plain;charset=utf-8";
const char kUtf8String[] = "UTF8_STRING";

const char* kSelectionDataAtoms[] = {
    Clipboard::kMimeTypeHTML,
    kString,
    kText,
    kTextPlain,
    kTextPlainUtf8,
    kUtf8String,
    NULL
};

std::vector<::Atom> GetTextAtomsFrom(const X11AtomCache* atom_cache)
{
    std::vector<::Atom> atoms;
    atoms.push_back(atom_cache->GetAtom(kUtf8String));
    atoms.push_back(atom_cache->GetAtom(kString));
    atoms.push_back(atom_cache->GetAtom(kText));
    atoms.push_back(atom_cache->GetAtom(kTextPlain));
    atoms.push_back(atom_cache->GetAtom(kTextPlainUtf8));
    return atoms;
}

std::vector<::Atom> GetURLAtomsFrom(const X11AtomCache* atom_cache)
{
    std::vector<::Atom> atoms;
    atoms.push_back(atom_cache->GetAtom(Clipboard::kMimeTypeURIList));
    atoms.push_back(atom_cache->GetAtom(kMimeTypeMozillaURL));
    return atoms;
}

std::vector<::Atom> GetURIListAtomsFrom(const X11AtomCache* atom_cache)
{
    std::vector<::Atom> atoms;
    atoms.push_back(atom_cache->GetAtom(Clipboard::kMimeTypeURIList));
    return atoms;
}

void GetAtomIntersection(const std::vector<::Atom>& desired,
    const std::vector<::Atom>& offered,
    std::vector<::Atom>* output)
{
    for (std::vector<::Atom>::const_iterator it = desired.begin();
         it != desired.end(); ++it) {
        std::vector<::Atom>::const_iterator jt = std::find(offered.begin(), offered.end(), *it);
        if (jt != offered.end())
            output->push_back(*it);
    }
}

void AddString16ToVector(const base::string16& str,
    std::vector<unsigned char>* bytes)
{
    const unsigned char* front = reinterpret_cast<const unsigned char*>(str.data());
    bytes->insert(bytes->end(), front, front + (str.size() * 2));
}

std::vector<std::string> ParseURIList(const SelectionData& data)
{
    // uri-lists are newline separated file lists in URL encoding.
    std::string unparsed;
    data.AssignTo(&unparsed);
    return base::SplitString(
        unparsed, "\n", base::KEEP_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
}

std::string RefCountedMemoryToString(
    const scoped_refptr<base::RefCountedMemory>& memory)
{
    if (!memory.get()) {
        NOTREACHED();
        return std::string();
    }

    size_t size = memory->size();
    if (!size)
        return std::string();

    const unsigned char* front = memory->front();
    return std::string(reinterpret_cast<const char*>(front), size);
}

base::string16 RefCountedMemoryToString16(
    const scoped_refptr<base::RefCountedMemory>& memory)
{
    if (!memory.get()) {
        NOTREACHED();
        return base::string16();
    }

    size_t size = memory->size();
    if (!size)
        return base::string16();

    const unsigned char* front = memory->front();
    return base::string16(reinterpret_cast<const base::char16*>(front), size / 2);
}

///////////////////////////////////////////////////////////////////////////////

SelectionFormatMap::SelectionFormatMap() { }

SelectionFormatMap::~SelectionFormatMap() { }

void SelectionFormatMap::Insert(
    ::Atom atom,
    const scoped_refptr<base::RefCountedMemory>& item)
{
    data_.erase(atom);
    data_.insert(std::make_pair(atom, item));
}

ui::SelectionData SelectionFormatMap::GetFirstOf(
    const std::vector<::Atom>& requested_types) const
{
    for (std::vector<::Atom>::const_iterator it = requested_types.begin();
         it != requested_types.end(); ++it) {
        const_iterator data_it = data_.find(*it);
        if (data_it != data_.end()) {
            return SelectionData(data_it->first, data_it->second);
        }
    }

    return SelectionData();
}

std::vector<::Atom> SelectionFormatMap::GetTypes() const
{
    std::vector<::Atom> atoms;
    for (const_iterator it = data_.begin(); it != data_.end(); ++it)
        atoms.push_back(it->first);

    return atoms;
}

///////////////////////////////////////////////////////////////////////////////

SelectionData::SelectionData()
    : type_(None)
    , atom_cache_(gfx::GetXDisplay(), kSelectionDataAtoms)
{
}

SelectionData::SelectionData(
    ::Atom type,
    const scoped_refptr<base::RefCountedMemory>& memory)
    : type_(type)
    , memory_(memory)
    , atom_cache_(gfx::GetXDisplay(), kSelectionDataAtoms)
{
}

SelectionData::SelectionData(const SelectionData& rhs)
    : type_(rhs.type_)
    , memory_(rhs.memory_)
    , atom_cache_(gfx::GetXDisplay(), kSelectionDataAtoms)
{
}

SelectionData::~SelectionData() { }

SelectionData& SelectionData::operator=(const SelectionData& rhs)
{
    type_ = rhs.type_;
    memory_ = rhs.memory_;
    // TODO(erg): In some future where we have to support multiple X Displays,
    // the following will also need to deal with the display.
    return *this;
}

bool SelectionData::IsValid() const
{
    return type_ != None;
}

::Atom SelectionData::GetType() const
{
    return type_;
}

const unsigned char* SelectionData::GetData() const
{
    return memory_.get() ? memory_->front() : NULL;
}

size_t SelectionData::GetSize() const
{
    return memory_.get() ? memory_->size() : 0;
}

std::string SelectionData::GetText() const
{
    if (type_ == atom_cache_.GetAtom(kUtf8String) || type_ == atom_cache_.GetAtom(kText) || type_ == atom_cache_.GetAtom(kTextPlainUtf8)) {
        return RefCountedMemoryToString(memory_);
    } else if (type_ == atom_cache_.GetAtom(kString) || type_ == atom_cache_.GetAtom(kTextPlain)) {
        std::string result;
        base::ConvertToUtf8AndNormalize(RefCountedMemoryToString(memory_),
            base::kCodepageLatin1,
            &result);
        return result;
    } else {
        // BTW, I looked at COMPOUND_TEXT, and there's no way we're going to
        // support that. Yuck.
        NOTREACHED();
        return std::string();
    }
}

base::string16 SelectionData::GetHtml() const
{
    base::string16 markup;

    if (type_ == atom_cache_.GetAtom(Clipboard::kMimeTypeHTML)) {
        const unsigned char* data = GetData();
        size_t size = GetSize();

        // If the data starts with 0xFEFF, i.e., Byte Order Mark, assume it is
        // UTF-16, otherwise assume UTF-8.
        if (size >= 2 && reinterpret_cast<const uint16_t*>(data)[0] == 0xFEFF) {
            markup.assign(reinterpret_cast<const uint16_t*>(data) + 1,
                (size / 2) - 1);
        } else {
            base::UTF8ToUTF16(reinterpret_cast<const char*>(data), size, &markup);
        }

        // If there is a terminating NULL, drop it.
        if (!markup.empty() && markup.at(markup.length() - 1) == '\0')
            markup.resize(markup.length() - 1);

        return markup;
    } else {
        NOTREACHED();
        return markup;
    }
}

void SelectionData::AssignTo(std::string* result) const
{
    *result = RefCountedMemoryToString(memory_);
}

void SelectionData::AssignTo(base::string16* result) const
{
    *result = RefCountedMemoryToString16(memory_);
}

} // namespace ui
